前两天面试在KVC的问题上有所欠缺,对于底层了解不够,因此写下这篇文章。这篇文章主要是使用KVC对Key的搜索原理,自己写代码来实现KVC。
关于Apple是如何实现KVC这个问题:
🙃 🙃 我也不知道……
因为没有办法打印出调用过的函数,所以KVC背后的一切还是很神秘的,只能通过几个有限的API来猜想,
对于setValue:forKey:
,在setter存在的情况,KVC 会直接发送set<Key>:
消息赋值;
在setter不在的情况下,且accessInstanceVariablesDirectly
方法是true的情况下,会按(_<key>, _<isKey>, <key>, is<Key>)
的顺序去查找实例变量,检查其是否存在之后通过object_setIvar
给它赋值。
如果都没有就调用setValue:forUNdefinedKey:
抛出错误。
对于setValue:forKeyPath:
,递归地检查keyPath中的属性是否存在,到达目标路径时再调用setValue:forKey:
完成赋值。
大致上我准备按上面的执行顺序进行实现,由于集合、字典和模型、KeyPath的情况比较复杂,所以我只实现了最普通的存值和取值,下面进入正题:
|
|
以上是根据KVC执行顺序自定义的一段代码,当然也省略了一些功能,总体上的逻辑还是比较清楚的,但是有一点问题,在使用object_setIvar
和object_getIvar
这两个runtime函数时,程序会频繁崩溃,而自己对runtime认识不足,以后再完整这段代码吧。
关于KVC,除去存取方法,底层执行顺序,错误处理以外,还有几点需要了解:
Value合法性校验
KVC提供了校验key对应的value是否合法的方法:
|
|
如果使用这个方法,需要进行重写,它的使用方法如下:
|
|
这样就实现了先对传入值的合法性进行校验,无误后再进行赋值,如上面的代码,当我们需要验证能不能用KVC设定某个值时,可以将其重写后调用
validateValue: forKey:
这个方法来验证,那么KVC就会直接调用这个方法来返回。需要注意的是即使重写了该方法,如果我们没有主动进行调用,KVC也不会主动去做验证,所以这个方法需要手动调用。
KVC的应用场景
作为iOS平台的知名黑魔法,KVC有很多奇特的作用,KVC在iOS开发中是绝不可少的利器,这种基于运行时的编程方式极大地提高了灵活性,简化了代码,它的常见作用有以下几点:
动态的存值和取值
🙃 🙃 🙃 🙃 🙃 🙃 🙃 🙃 🙃 🙃 🙃 🙃 🙃 🙃 🙃 🙃 🙃 🙃 🙃 🙃 🙃
访问和修改私有的成员
对于类里的私有属性,无论Swift还是Objective-C都是无法直接访问的,但是KVC是可以的。
Model和字典的互相转换
这是KVC又一个强大之处,只需要很少的代码量即可完成很多功能。
修改控件的内部属性
这也是iOS开发中必不可少的小技巧。众所周知很多UI控件都由很多内部UI控件组合而成的,但是Apple没有提供这访问这些控件的API,这样我们就无法正常地访问和修改这些控件的样式。
而KVC在大多数情况可下可以解决这个问题。比如个性化UITextField中的placeHolderText,一般情况下可以运用runtime来获取Apple不想开放的属性名:
|
|
可以从里面看到其他还有很多东西可以修改,运用KVC设值可以获得自己想要的效果。
KVC操作集合
Apple对KVC的valueForKey:
方法作了一些特殊的实现,比如说NSArray和NSSet这样的容器类就实现了这些方法,所以可以用KVC很方便地操作集合。
高阶信息传递
当对容器类使用KVC时,valueForKey:
将会被传递给容器中的每一个对象,而不是容器本身进行操作。结果会被添加进返回的容器中,这样,开发者可以很方便的操作集合来返回另一个集合:
|
|
方法capitalizedString
被传递到NSArray中的每一项,这样,NSArray的每一员都会执行capitalizedString
并返回一个包含结果的新的NSArray。从打印结果可以看出,所有String都成功以转成了大写。
同样如果要执行多个方法也可以用valueForKeyPath:
方法。它先会对每一个成员调用 capitalizedString
方法,然后再调用length,因为lenth方法返回是一个数字,所以返回结果以NSNumber的形式保存在新数组里。
KVO
当然了,KVC除了传值取值,访问和修改私有变量,修改控件属性,操作集合、字典和模型,更重要的就是KVO了。